﻿using PMS.Core.Caching;
using PMS.Core.Configuration.CommonEnumConfigs;
using PMS.Data.DbUtils;
using PMS.Data.Entities.Customers;
using PMS.Data.Entities.Settings;
using PMS.Data.Entities.Stores;
using PMS.Data.Entities.SystemModules;
using PMS.Services.Caching;
using PMS.Services.Caching.Extensions;
using PMS.Services.EntityServices.Customers;
using PMS.Services.EntityServices.Stores;
using PMS.Services.Utils;
using System;
using System.Collections.Generic;
using System.Linq;

namespace PMS.Services.EntityServices.SystemModules
{
    public class SystemModuleService : ISystemModuleService
    {
        #region Fields

        private readonly AdminAreaSettings _adminAreaSettings;
        private readonly ICacheKeyService _cacheKeyService;
        private readonly IStaticCacheManager _cacheManager;
        private readonly IEventDispatcher _eventDispatcher;
        private readonly ICustomerService _customerService;
        private readonly IStaticCacheManager _staticCacheManager;
        private readonly IStoreMappingService _storeMappingService;
        private readonly IRepository<ModuleButton> _moduleBtnRepository;
        private readonly IRepository<SystemModule> _systemModuleRepository;
        private readonly IRepository<StoreMapping> _storeMappingRepository;
        private readonly IRepository<SystemModuleCustomerRoleMapping> _moduleroleMappingRepository;

        #endregion

        #region Ctor

        public SystemModuleService(AdminAreaSettings adminAreaSettings,
            ICacheKeyService cacheKeyService,
            ICustomerService customerService,
            IEventDispatcher eventDispatcher,
            IStaticCacheManager cacheManager,
            IStaticCacheManager staticCacheManager,
            IStoreMappingService storeMappingService,
            IRepository<ModuleButton> moduleBtnRepository,
            IRepository<SystemModule> systemModuleRepository,
            IRepository<StoreMapping> storeMappingRepository,
            IRepository<SystemModuleCustomerRoleMapping> moduleroleMappingRepository)
        {
            _adminAreaSettings = adminAreaSettings;
            _cacheManager = cacheManager;
            _cacheKeyService = cacheKeyService;
            _customerService = customerService;
            _eventDispatcher = eventDispatcher;
            _staticCacheManager = staticCacheManager;
            _storeMappingService = storeMappingService;
            _moduleBtnRepository = moduleBtnRepository;
            _systemModuleRepository = systemModuleRepository;
            _storeMappingRepository = storeMappingRepository;
            _moduleroleMappingRepository = moduleroleMappingRepository;
        }

        #endregion

        #region Utils
        private IList<SystemModule> LoadSystemModuleFunc(int[] rolesid, int storeId, bool showHidden = false) 
        {
            var query = from mp in _moduleroleMappingRepository.Table //.Where(o => rolesid.Contains(o.CustomerRoleId))
                        where rolesid.Contains(mp.CustomerRoleId)
                        join m in _systemModuleRepository.Table
                        on mp.SystemModuleId equals m.Id into tempinnerjoin
                        from j in tempinnerjoin.DefaultIfEmpty()
                        where j.IsActive && (showHidden || mp.ViewPermission)
                        orderby j.ParentModuleId, j.DisplayOrder
                        select j;
            //Filter SystemModules which Not Limited to store
            var authedModules = query.Distinct().ToList();
            if (!(authedModules?.Any() ?? false)) return null;
            return authedModules.Where(module => _storeMappingService.Authorize(module, storeId)).ToList();
        }
        #endregion

        #region Methods

        #region SystemModule
        /// <summary>
        /// Gets all SystemModules within Store
        /// </summary>
        /// <param name="storeId">Store identifier</param>
        /// <returns>system module list</returns>
        public virtual IList<SystemModule> GetAllSystemModulesInStore(int storeId)
        {
            if (storeId <= 0)
                throw new ArgumentNullException("StoreId Unspecified from GetAllSystemModulesInStore");
            var key = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.SystemModulesAllCacheKey, storeId);

            //后台系统模块菜单加载方式： Union
            //即所有未指定StoreId的系统模块菜单(公共系统模块菜单) 及 指定Store专属的系统模块菜单

            //Step1: Load All SystemModules for all stores
            var modulesforallQuery = _systemModuleRepository.Table.Where(m => m.IsActive && !m.LimitedToStores);
            //Step2: Load All SystemModules limited to specific store
            var modulesforstoreQuery = from m in _systemModuleRepository.Table
                                       where m.IsActive && m.LimitedToStores
                                       join sm in _storeMappingRepository.Table
                                       on new
                                       {
                                           c1 = m.Id,
                                           c2 = nameof(SystemModule)
                                       }
                                       equals new
                                       {
                                           c1 = sm.EntityId,
                                           c2 = sm.EntityName
                                       }
                                       into tSm
                                       from sm in tSm.DefaultIfEmpty()
                                       where storeId == sm.StoreId
                                       select m;
            //now union them together
            var query = modulesforallQuery.Union(modulesforstoreQuery);
            query = query.Distinct().OrderBy(m => m.ParentModuleId).ThenBy(m => m.DisplayOrder); 
            return query.ToCachedList(key);
        }

        /// <summary>
        /// 获取当前用户所有有权限查看的系统菜单模块
        /// 根据AdminAreaSettings.DisplayAllSystemMenus设置提供两种模式的后台系统菜单显示
        /// True: 则全量显示后台系统菜单，当用户打开页面时根据用户权限展示内容或显示无操作权限等信息
        /// False:按用户角色分配的权限加载有查看权限的系统模块菜单展示
        ///
        /// </summary>
        /// <param name="customerId"></param>
        /// <returns></returns>
        public virtual IList<SystemModule> GetSysteModulesByCustomer(int storeId, int customerId)
        {
            if (_adminAreaSettings.DisplayAllSystemMenus)
                return GetAllSystemModulesInStore(storeId);
            var customer = _customerService.GetCustomerById(customerId);
            if (customer == null)
                return null;

            var key = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.CustomerSystemModulesCacheKey, customerId);
            return _cacheManager.Get(key, () =>
            {
                return customer.IsAdministrator(_customerService) 
                    ? GetAllSystemModulesInStore(storeId)
                    : LoadSystemModuleFunc(_customerService.GetCustomerRoleIds(customer), storeId);
            });
        }

        /// <summary>
        /// Get SystemModule List By Role
        /// </summary>
        /// <param name="storeId"></param>
        /// <param name="roleId"></param>
        /// <param name="showHidden">whether show SystemModules when "ViewPermission" off to CustomerRole</param>
        /// <returns></returns>
        public virtual IList<SystemModule> GetSysteModulesByRole(CustomerRole role, Store store, bool showHidden = false)
        {
            if (role == null) return null;

            var key = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.SystemModulesByCustomerRoleCacheKey, role.Id);
            return _cacheManager.Get(key, () => 
            {
                return role.RoleType == RoleType.Administrator
                ? GetAllSystemModulesInStore(store.Id)
                : LoadSystemModuleFunc(new int[] { role.Id }, store.Id, showHidden);
            });
        }
        /// <summary>
        /// Gets a SystemModule
        /// </summary>
        /// <param name="moduleId">Message template identifier</param>
        /// <returns>Message template</returns>
        public virtual SystemModule GetSystemModuleById(int moduleId)
        {
            if (moduleId == 0)
                return null;

            return _systemModuleRepository.ToCachedGetById(moduleId);
        }

        /// <summary>
        /// Insert a SystemModule entity
        /// </summary>
        /// <param name="module">SystemModule</param>
        public virtual SystemModule InsertSystemModule(SystemModule moduleEntity)
        {
            if (moduleEntity == null)
                throw new ArgumentNullException(nameof(moduleEntity));

            var entity = _systemModuleRepository.Insert(moduleEntity);
            //event notification for Cache
            _eventDispatcher.EntityInserted(entity);
            return entity;
        }

        /// <summary>
        /// Delete a SystemModule and its RoleMapping, StoreMapping
        /// </summary>
        /// <param name="moduleEntity"></param>
        public virtual void DeleteSystemModule(SystemModule moduleEntity)
        {
            if (moduleEntity == null)
                throw new ArgumentNullException(nameof(moduleEntity));

            RemoveSystemModuleCustomerRoleMapping(moduleId: moduleEntity.Id);
            if(moduleEntity.LimitedToStores)
            {
                var storeMapping = new StoreMapping
                {
                    EntityId = moduleEntity.Id,
                    EntityName = nameof(SystemModule)
                };
                _storeMappingRepository.Delete(sm => sm.EntityId == storeMapping.EntityId && sm.EntityName == storeMapping.EntityName);
                //event notification
                _eventDispatcher.EntityDeleted(storeMapping);
            }
            _systemModuleRepository.Delete(moduleEntity);

            //event notification for Cache
            _eventDispatcher.EntityDeleted(moduleEntity);
        }

        /// <summary>
        /// Update SystemModule Entity
        /// </summary>
        /// <param name="moduleEntity"></param>
        public virtual void UpdateSystemModule(SystemModule moduleEntity)
        {
            if (moduleEntity == null)
                throw new ArgumentNullException(nameof(moduleEntity));

            moduleEntity.UpdatedOnTimeUtc = DateTime.UtcNow;
            _systemModuleRepository.Update(moduleEntity);
            //event notification For Cache
            _eventDispatcher.EntityUpdated(moduleEntity);
        }
        #endregion

        #region SystemModule-CustomerRole Mappings
        /// <summary>
        /// Get All SystemModule-CustomerRole Mappings of all CustomerRoles By SystemModuleId
        /// </summary>
        /// <param name="moduleId"></param>
        /// <returns></returns>
        public virtual IList<SystemModuleCustomerRoleMapping> GetSystemModuleCustomerRoleMappingsBySystemModuleId(int moduleId)
        {
            var key = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.ModuleRoleMappingsByModuleIdCacheKey, moduleId);
            return _moduleroleMappingRepository.Table.Where(mp => mp.SystemModuleId == moduleId).ToCachedList(key);
        }

        /// <summary>
        /// Get SystemModuleCustomerRoleMapping List By CustomerRole
        /// </summary>
        /// <param name="roleId"></param>
        /// <returns></returns>
        public virtual IList<SystemModuleCustomerRoleMapping> GetSystemModuleCustomerRoleMappingsByCustomerRole(CustomerRole role)
        {
            if (role == null) return null;
            var key = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.ModuleRoleMappingsByCustomerRoleCacheKey, role.Id);
            var query = _moduleroleMappingRepository.Table.Where(mp => mp.CustomerRoleId == role.Id);
            return query.ToCachedList(key);
        }

        /// <summary>
        /// Add a system module-customer role mapping
        /// </summary>
        /// <param name="moduleroleMapping">SystemModule-customer role mapping</param>
        public virtual void AddSystemModuleCustomerRoleMapping(SystemModuleCustomerRoleMapping moduleroleMapping)
        {
            if (moduleroleMapping is null)
                throw new ArgumentNullException(nameof(moduleroleMapping));

            _moduleroleMappingRepository.Insert(moduleroleMapping);

            _eventDispatcher.EntityInserted(moduleroleMapping);
        }

        /// <summary>
        /// Update a system module-customer role mapping
        /// </summary>
        /// <param name="moduleroleMapping">SystemModule-customer role mapping</param>
        public virtual void UpdatedSystemModuleCustomerRoleMapping(SystemModuleCustomerRoleMapping moduleroleMapping)
        {
            if (moduleroleMapping is null)
                throw new ArgumentNullException(nameof(moduleroleMapping));

            _moduleroleMappingRepository.Update(moduleroleMapping);

            _eventDispatcher.EntityUpdated(moduleroleMapping);
        }

        /// <summary>
        /// 更新同一用户角色下的所有系统模块权限
        /// </summary>
        /// <param name="role"></param>
        /// <param name="mappings"></param>
        public virtual void UpdateSystemModuleCustomerRoleMappingsWithSameCustomerRole(CustomerRole role, IList<SystemModuleCustomerRoleMapping> mappings)
        {
            if (role == null)
                throw new ArgumentNullException(nameof(role));
            if(!mappings?.Any() ?? true)
                throw new ArgumentNullException(nameof(mappings));

            _moduleroleMappingRepository.Update(mappings);
            _eventDispatcher.EntityUpdated(new SystemModuleCustomerRoleMapping 
            { 
                SystemModuleId = 0,
                CustomerRoleId = role.Id,  //Only Useful for CustomerRole
                ViewPermission = false,
                AuthorizedButtonIds = string.Empty
            });
        }
        /// <summary>
        /// Remove a system module-customer role mapping
        /// </summary>
        /// <param name="module">SystemModule</param>
        /// <param name="role">Customer role</param>
        public virtual void RemoveSystemModuleCustomerRoleMapping(SystemModule module, CustomerRole role)
        {
            if (module is null)
                throw new ArgumentNullException(nameof(module));

            if (role is null)
                throw new ArgumentNullException(nameof(role));

            var mapping = _moduleroleMappingRepository.Table.SingleOrDefault(ccrm => ccrm.SystemModuleId == module.Id && ccrm.CustomerRoleId == role.Id);

            if (mapping != null)
            {
                _moduleroleMappingRepository.Delete(mapping);

                //event notification
                _eventDispatcher.EntityDeleted(mapping);
            }
        }

        /// <summary>
        /// Remove system module-customer role mappings by systemmoduleId OR roleId
        /// </summary>
        /// <param name="moduleId"></param>
        /// <param name="roleId"></param>
        public virtual void RemoveSystemModuleCustomerRoleMapping(int? moduleId = null, int? roleId = null)
        {
            var mapping = new SystemModuleCustomerRoleMapping();
            if (moduleId.HasValue)
                if(moduleId.Value > 0)
                {
                    _moduleroleMappingRepository.Delete(m => m.SystemModuleId == moduleId.Value);
                    mapping.SystemModuleId = moduleId.Value;
                }

            if (roleId.HasValue)
                if (roleId.Value > 0)
                {
                    _moduleroleMappingRepository.Delete(m => m.CustomerRoleId == roleId.Value);
                    mapping.CustomerRoleId = roleId.Value;
                }
            //event notification
            if(mapping.SystemModuleId > 0 || mapping.CustomerRoleId > 0)
                _eventDispatcher.EntityDeleted(mapping);
        }
        #endregion

        #region ModuleButtons
        /// <summary>
        /// 通过Id数组获取指定系统模块所分配的按钮
        /// </summary>
        /// <param name="moduleId">systemmodule id for cachekey</param>
        /// <param name="btnIds"></param>
        /// <returns></returns>
        public virtual IList<ModuleButton> GetModuleAssignedButtonsByIds(int moduleId, int[] btnIds)
        {
            if(moduleId <= 0)
                throw new ArgumentNullException("moduleId Unspecified from GetModuleAssignedButtonsByIds");
            if (!btnIds?.Any() ?? false)
                throw new ArgumentNullException("btnIds Unspecified from GetModuleAssignedButtonsByIds");
            var key = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.ModuleAssignedButtonsCachKey, moduleId);

            var query = from b in _moduleBtnRepository.Table
                        where b.IsActive && btnIds.Contains(b.Id)
                        orderby b.GroupType, b.DisplayOrder
                        select b;

            return query.Distinct().ToCachedList(key);
        }

        /// <summary>
        /// 获取所有系统模块按钮
        /// </summary>
        /// <returns></returns>
        public virtual IList<ModuleButton> GetAllModuleButtons()
        {
            var key = CacheKeyConfigs.ModuleButtonsAllCacheKey;
            var query = _moduleBtnRepository.Table.Where(b => b.IsActive);
            return query.ToCachedList(key);
        }
        
        #endregion
        #endregion
    }
}
